using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace Fadd.Commands.Net
{
    /// <summary>
    /// Receives commands from the client and invoked them in the server dispatcher.
    /// </summary>
    public class CommandServer
    {
        private TcpListener _listener;
        private readonly List<CommandChannel> _clients = new List<CommandChannel>();
        private readonly CommandManager _cmdMgr;

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandServer"/> class.
        /// </summary>
        /// <param name="mgr">Manager that the incoming commands are invoked in.</param>
        public CommandServer(CommandManager mgr)
        {
            Check.Require(mgr, "mgr");
            _cmdMgr = mgr;
        }

        /// <summary>
        /// Starts the server.
        /// </summary>
        /// <param name="address">IP address that we accept incoming connections on.</param>
        /// <param name="port">Port that the server accepts new connections on.</param>
        public void Start(IPAddress address, int port)
        {
            if (_listener != null)
                throw new InvalidOperationException("CommandServer is already started.");

            _listener = new TcpListener(address, port);
            _listener.Start(10);
            _listener.BeginAcceptSocket(OnAcceptSocket, null);
        }

        /// <summary>
        /// Stops the server.
        /// </summary>
        public void Stop()
        {
            lock (_clients)
            {
                foreach (CommandChannel client in _clients)
                    client.Dispose();
                _clients.Clear();                
            }

            _listener.Stop();
        }

        private void OnAcceptSocket(IAsyncResult ar)
        {
            Socket socket;
            try
            {
                socket = _listener.EndAcceptSocket(ar);
                _listener.BeginAcceptSocket(OnAcceptSocket, null);
            }
            catch (SocketException)
            {
                return;
            }
            catch(ObjectDisposedException)
            {
                return;
            }

            CommandChannel tunnel = new CommandChannel(_cmdMgr, socket);
            tunnel.Disconnected += OnClientDisconnected;
            tunnel.AutoReconnect = false;
            lock (_clients)
                _clients.Add(tunnel);
        }

        private void OnClientDisconnected(object source, DisconnectedEventArgs args)
        {
            lock (_clients)
            {
                CommandChannel tunnel = (CommandChannel) source;
                tunnel.Disconnected -= OnClientDisconnected;
                _clients.Remove(tunnel);
            }
        }
    }
}
